package edu.ntu.cloudroid.xacml;

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.datanucleus.sco.simple.Map;


import com.sun.xacml.Obligation;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;

import edu.ntu.cloudroid.model.IDs;
import edu.ntu.cloudroid.model.data.AbstractData;
import edu.ntu.cloudroid.model.data.DetachedData;
import edu.ntu.cloudroid.model.data.LocationInfo;
import edu.ntu.cloudroid.model.data.VitalSignInfo;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.nongui.com.RequestNoInputs;
import edu.ntu.cloudroid.server.RequestServlet;
import edu.ntu.cloudroid.server.backendservice.DataService;
import edu.ntu.cloudroid.server.backendservice.FriendService;
import edu.ntu.cloudroid.server.backendservice.PolicyService;
import edu.ntu.cloudroid.xacml.builder.request.RequestBuilder;
import edu.ntu.cloudroid.xacml.obligation.DefaultObligation;
import edu.ntu.cloudroid.xacml.obligation.ObligationHandle;

public class ClouDroidPEP
{
	private static final Logger logger = Logger.getLogger(ClouDroidPEP.class
			.getName());

	private final ClouDroidPDP pdp;

	public ClouDroidPEP()
	{
		pdp = new ClouDroidPDP();
	}
	
	/**
	 * All access control decision is made here. 
	 * If permit, it processes the obligations, according to the
	 * obligation type
	 */
	public HashMap<String, List<DetachedData>> evaluateRequest(
			ProfileData subject, ProfileData resource, RequestNoInputs request)
			throws Exception {
		
		long start = System.currentTimeMillis();
		//1. Build the request
		RequestBuilder requestBuilder = new RequestBuilder(subject, resource,
				request.getInputAttributes());
		RequestCtx requestCtx = requestBuilder.getRequest();
		
		//2. Get the PDP to evaluate
		ResponseCtx response = pdp.evaluate(requestCtx);
		long end = System.currentTimeMillis();
		RequestServlet.logger.info("XACML time = "+(end-start));
		if (response.getResults().size()==0){
			return null;
		}
	
		//3. Take the first (PERMIT) result
		Iterator<Result> it = response.getResults().iterator();
		Result result = it.next();		
		if (result.getDecision()!=Result.DECISION_PERMIT){
			RequestServlet.logger.info("NOT permit decision");
			return null;
		}
		
		HashMap<String, List<DetachedData>> returnedMap = new HashMap<String, List<DetachedData>>();
		//4. Check obligations
		Set<Obligation> obligations = result.getObligations();
		start = System.currentTimeMillis();
		if (obligations.size()==0){
			//4.1. If no obligation, return the all data from the latest timestamp
			//and update the latest access		
			Date latestTimestamp = FriendService.getFriendTimeStamp(resource.getEmailAddress(), subject.getEmailAddress());
					
			
			List<DetachedData> list = new ArrayList<DetachedData>();
			List<AbstractData> dataList = DataService.getData(resource,
					latestTimestamp, resource.getDataId());
			Date newTs = dataList.get(dataList.size()-1).getTimestamp();
			FriendService.delayTimestampUpdate(resource.getEmailAddress(),subject.getEmailAddress(), newTs);
			FriendService.updateFriendTimeStamp(resource.getEmailAddress(),subject.getEmailAddress());
			for (AbstractData ad : dataList){
				list.add(ad.detach(-1));
			}		
			returnedMap.put(IDs.ObligationID.NoObligation.toString(), list);
			return returnedMap;
		}
	
		//usually, there's only one obligation
		for (Obligation ob : obligations){
			List<DetachedData> list = new ArrayList<DetachedData>();	
			//if default obligation
			if (ob.getId().equals(new URI(
		               IDs.ObligationID.DefaultObligation.toString()))){
				List<DetachedData> sl = new DefaultObligation()
						.processObligation(subject, resource, requestCtx, ob);
				for (DetachedData d : sl)
					list.add(d);
				returnedMap.put(IDs.ObligationID.DefaultObligation.toString(),
						list);
			}
			ObligationHandle obh = IDs.getObligationHandle(ob.getId().toString());
			
			List<DetachedData> sl = obh.processObligation(subject, resource, requestCtx, ob);
			for (DetachedData d : sl)
				list.add(d);
			returnedMap.put(ob.getId().toString(), list);
		}
		FriendService.updateFriendTimeStamp(resource.getEmailAddress(), subject.getEmailAddress());
		end = System.currentTimeMillis();
		RequestServlet.logger.info("data request time = "+(end-start));
		return returnedMap;
	}	
}
